package indzcraft.calc;

import java.util.HashMap;


public class Calculator extends Calc {
	
	public Stats stats;
	public Glyphs glyphs;
	public Race race;
	public GearBuffs gearbuffs;
	public Weapon mh;
	public Weapon oh;
	
	public ProcDB procDB = new ProcDB();
	
	// variables
	double reaction_time = 1;
	
	// some globals for easier use
	double PRECISION = Math.pow(10, -8);
	double mh_swings_per_sec = 0.;
	double oh_swings_per_sec = 0.;
	double total_crits = 0.;
	double ks_ori_cd = 120.;
	double ar_ori_cd = 180.;
	double ks_actual_cd = ks_ori_cd;
	double ar_actual_cd = ar_ori_cd;
	HashMap<String,Double> aps = new HashMap<String,Double>();
	HashMap<String,Double> dps = new HashMap<String,Double>();
	double avg_ss_for_4cp;
	double avg_rs_for_4cp;
	double avg_ss_for_5cp;
	double avg_ss;
	double avg_rs;
	double ss_energycost;
	double rvs_energycost;
	double avg_energycost;
		
	public Calculator(Stats stats, Weapon mh, Weapon oh, Glyphs glyphs, Race race, GearBuffs gearbuffs)
	{
		super(stats,mh,oh,glyphs,race,gearbuffs);
		this.stats = stats;
		this.mh = mh;
		this.oh = oh;
		this.glyphs = glyphs;
		this.race = race;
		this.gearbuffs = gearbuffs;
		get_dps();
	}
	
	private double get_dps()
	{	
		calc_avg_rvs_per_finisher();
		
		while(true) {
			
			double t13_2pc = this.gearbuffs.get("t13_2pc");
			double evi_energycost = (35-35*0.2*6/31*t13_2pc)/this.stats.get_style_hit_chance(race,mh);
			//double rup_energycost = (25-25*0.2*6/31*t13_2pc)/this.stats.get_style_hit_chance(race,mh);
			double cps_snd = 5;
			double avg_ss_per_snd = (cps_snd/1.2+(cps_snd-1)/1.2)/2;
			double avg_full_energy_per_snd = avg_ss_per_snd * ss_energycost + 25-25*0.2*6/31*t13_2pc - 5*this.stats.talents.get("relentless_strikes")/3*5;
			double avg_full_energy_per_evi = avg_energycost + evi_energycost - 5 * this.stats.talents.get("relentless_strikes") / 3 * 5;
			//double avg_full_energy_per_rup = avg_energycost + rup_energycost-5*3/3*5;
			
			double snd_ramp_time = avg_full_energy_per_snd / get_energy_regen();
			double evi_ramp_time = avg_full_energy_per_evi / get_energy_regen();
			//double rup_ramp_time = avg_full_energy_per_rup / get_energy_regen();
			
			double snd_length = (6+6*this.glyphs.slice_and_dice)*(1+0.25*this.stats.talents.get("imp_snd")) + 3*(1+0.25*2) * 5 - this.reaction_time;
			//double snd_length_minus1 = (6+6*this.glyphs.slice_and_dice)*(1+0.25*2) + 3*(1+0.25*2) * 4 - this.reaction_time;
			
			double evi_rup_time = snd_length - snd_ramp_time;
			double evi_per_cycle = evi_rup_time / evi_ramp_time;
			
			double avg_ss_per_cycle = avg_ss_per_snd + evi_per_cycle * avg_ss; // + B185*UseRupture
			double avg_rs_per_cycle = evi_per_cycle * avg_rs; // +B186*UseRupture
			
			double ss_per_sec = avg_ss_per_cycle / snd_length;
			double rs_per_sec = avg_rs_per_cycle / snd_length;
			double evi_per_sec = evi_per_cycle / snd_length;
			
			double ks_attacks_per_sec = this.stats.talents.get("killing_spree")*5/ks_actual_cd;
			
			double t13_4pc = this.gearbuffs.get("t13_4pc");
			double attacks_for_aa_cp_regen = 0.;
			if(aps.get("oh") != null)
				attacks_for_aa_cp_regen += aps.get("oh");
			if(aps.get("main_gauche") != null)
				attacks_for_aa_cp_regen += aps.get("main_gauche");
			double autoattack_cp_regen = stats.talents.get("combat_potency") * attacks_for_aa_cp_regen;
			double ar_bonus_cp_regen = autoattack_cp_regen * 0.2;
			double ar_duration = 15 + 5 * this.glyphs.adrenaline_rush + 3 * t13_4pc;
			double ar_energy = ar_duration * (ar_bonus_cp_regen + 10 * (1+0.01*this.stats.get_haste_by_conversion())) * this.stats.talents.get("adrenaline_rush");
			double evis_per_ar = ar_energy / avg_full_energy_per_evi;
			double ar_evis_per_sec = evis_per_ar / ar_actual_cd;
			double ar_ss_per_sec = ar_evis_per_sec * avg_ss;
			double ar_rs_per_sec = ar_evis_per_sec * avg_rs;
			
			// legendary evis
			double dagger_stage3 = this.gearbuffs.get("dagger_stage3");
			double avg_proc_at = 40; // stacks
			double proc_interval = 1/(0.095*total_hits);
			//System.out.println("proc_interval " + proc_interval);
			double proc_after = avg_proc_at*proc_interval;
			double procs_per_sec = 1./proc_after;
			double extra_evis_per_proc = 6;
			double avg_extra_evis = procs_per_sec*extra_evis_per_proc*dagger_stage3;
			//System.out.println("avg_extra_evis " + avg_extra_evis);
								
			aps.put("sinister_strike", ss_per_sec + ar_ss_per_sec);
			aps.put("revealing_strike", rs_per_sec + ar_rs_per_sec);
			aps.put("eviscerate", evi_per_sec + ar_evis_per_sec + avg_extra_evis);
			aps.put("killing_spree", ks_attacks_per_sec);
			aps.put("deadly_poison", 1./3.);
			
			mh_swings_per_sec = get_total_speed()/this.mh.speed;
			oh_swings_per_sec = get_total_speed()/this.oh.speed;
			double mh_hits_per_sec = get_total_speed()/this.mh.speed * this.stats.get_mh_hit_chance(race,mh);
			double oh_hits_per_sec = get_total_speed()/this.oh.speed * this.stats.get_oh_hit_chance(race,oh);
			aps.put("mh", mh_hits_per_sec);
			aps.put("oh", oh_hits_per_sec);
			
			double total_mh_hits = aps.get("mh") + aps.get("sinister_strike") + aps.get("revealing_strike") + aps.get("eviscerate") + aps.get("killing_spree");
			double total_oh_hits = aps.get("oh") + aps.get("killing_spree");
			double mg_procs = this.stats.get_style_hit_chance(race,mh) * total_mh_hits * 0.02 * this.stats.get_mastery_by_conversion();
			aps.put("main_gauche", mg_procs);
			
			double extra_dp_procs = total_oh_hits * 0.3 * this.stats.get_spell_hit_chance();
			double ip_procs = this.stats.get_spell_hit_chance() * (total_mh_hits + mg_procs) * 0.2 * this.mh.speed / 1.4 + extra_dp_procs;
			aps.put("instant_poison", ip_procs);
			//System.out.println("3 " + Math.abs(total_hits - (total_mh_hits + total_oh_hits + mg_procs)));
			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits + mg_procs)) < PRECISION)
				break;
			
			total_hits = total_mh_hits + total_oh_hits + mg_procs;
			total_crits = (aps.get("main_gauche") + aps.get("mh") + aps.get("revealing_strike") + aps.get("eviscerate") + aps.get("killing_spree")*2 + aps.get("oh")) * this.stats.get_crit_chance();
			double t11_2pc = this.gearbuffs.get("t11_2pc");
			total_crits += aps.get("sinister_strike") * (this.stats.get_crit_chance() + 0.05*t11_2pc);
					
			// recalculating procs etc.
			double time_between_finisher = snd_length / (evi_per_cycle + (ar_evis_per_sec + avg_extra_evis) * snd_length);
			//System.out.println(time_between_finisher);
			
			double real_cd_reduce_per_sec = 1 + 5 * this.stats.talents.get("restless_blades") / time_between_finisher;
			
			ks_actual_cd = ks_ori_cd / real_cd_reduce_per_sec;
			ar_actual_cd = ar_ori_cd / real_cd_reduce_per_sec;
			
			double mh_landslide_uptime = 1 - Math.pow(1 - this.mh.speed / 60, 12 * (total_mh_hits + mg_procs));
			if(!this.mh.hasEnchant("landslide"))
				mh_landslide_uptime *= 0;
			double oh_landslide_uptime = 1 - Math.pow(1 - this.oh.speed / 60, 12 * (total_oh_hits));
			if(!this.oh.hasEnchant("landslide"))
				oh_landslide_uptime *= 0;
			
			double mh_hurricane_uptime = 1 - Math.pow(1 - this.mh.speed / 60, 12 * (total_mh_hits + mg_procs));
			if(!this.mh.hasEnchant("hurricane"))
				mh_hurricane_uptime *= 0;
			double oh_hurricane_uptime = 1 - Math.pow(1 - this.oh.speed / 60, 12 * (total_oh_hits));
			if(!this.oh.hasEnchant("hurricane"))
				oh_hurricane_uptime *= 0;
			
			//legendary stages
			double dagger_stage1 = this.gearbuffs.get("dagger_stage1");
			double dagger_stage2 = this.gearbuffs.get("dagger_stage2");
			dagger_stage3 = this.gearbuffs.get("dagger_stage3");
			double dagger_uptime = get_uptime_with_rampup(0.095,50,30);
			double dagger_uptime_3 = get_uptime_with_rampup_dagger3(0.095,40,30);
			this.stats.procs_agi = dagger_stage1 * dagger_uptime * 2 + dagger_stage2 * dagger_uptime * 5;
			this.stats.procs_agi += dagger_stage3 * dagger_uptime_3 * 17;
			
			// kiroptyric_sigil
			double kiroptyric_sigil = this.gearbuffs.get("kiroptyric_sigil");
			double kiroptyric_sigil_uptime = 15/(90+this.reaction_time);
			this.stats.procs_agi += kiroptyric_sigil * kiroptyric_sigil_uptime * 2290;
			
			this.stats.procs_ap = mh_landslide_uptime*1000 + oh_landslide_uptime*1000;
			
			this.stats.procs_haste = 0;
			//universal_procs();
			// universal procs
			String[] gearbuffs = this.gearbuffs.getGearBuffs();
			for(int i = 0;i < gearbuffs.length;i++){ // run through all gearbuffs
				if(this.procDB.has(gearbuffs[i])){
					ProcDB.Proc p = this.procDB.get(gearbuffs[i]);
					//System.out.println(gearbuffs[i] + " " + p.toString());
					
					double boost = p.boost(total_hits);
					if(p.stat.equals("haste"))
						this.stats.procs_haste += boost;
					else if(p.stat.equals("agi"))
						this.stats.procs_agi += boost;
					else if(p.stat.equals("ap"))
						this.stats.procs_ap += boost;
				}	
			}
			
			double t12_4pc = this.gearbuffs.get("t12_4pc");
			double t12_4pc_uptime = t12_4pc*30./(30+1);
			this.stats.t12_4pc_multiplier = 0.25 * t12_4pc_uptime / 3;
			
		}
		//System.out.println(this.stats.procs_haste);
		
		// fixed stuff
		double ks_uptime = this.stats.talents.get("killing_spree") * 2.5 / ks_actual_cd;
		
		double bg_singleduration = 1 / (aps.get("sinister_strike") + aps.get("revealing_strike")) * 4 / 3 * this.stats.talents.get("bandits_guile");
		double bg_length = 15 + 3 * bg_singleduration;
		double bg_uptime_0_10_20 = bg_singleduration / bg_length;
		double bg_uptime_30 = 15 / bg_length;
		double avg_bg_buff = (0+0.1+0.2)*bg_uptime_0_10_20+0.3*bg_uptime_30;

		double time_between_evis = 1 / aps.get("eviscerate");
		double time_between_auto_attacks = 1 / (aps.get("mh")+aps.get("oh"));
		double t11_4_uptime = 1 - Math.pow( (1-0.01) , time_between_evis/time_between_auto_attacks );
		int t11_4pc = this.gearbuffs.get("t11_4pc");
		t11_4_uptime *= t11_4pc;
		
		double dps_mh = mh_swings_per_sec * get_mh_dmg();
		double dps_mh_crit = dps_mh * 2.06 * this.stats.get_crit_chance();
		dps_mh = dps_mh * (this.stats.get_mh_hit_chance(race,mh)-0.24-this.stats.get_crit_chance()) + dps_mh * 0.75 * 0.24 + dps_mh * 2.06 * this.stats.get_crit_chance();
		dps_mh *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff) * (1 + (0.2 + 0.1*this.glyphs.killing_spree)*ks_uptime);
		dps_mh_crit *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff) * (1 + (0.2 + 0.1*this.glyphs.killing_spree)*ks_uptime); 
		dps.put("mh", dps_mh);
		
		double dps_oh = oh_swings_per_sec * get_oh_dmg();
		double dps_oh_crit = dps_oh * 2.06 * this.stats.get_crit_chance();
		dps_oh = dps_oh * (this.stats.get_oh_hit_chance(race,oh)-0.24-this.stats.get_crit_chance()) + dps_oh * 0.75 * 0.24 + dps_oh * 2.06 * this.stats.get_crit_chance();
		dps_oh *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff) * (1 + (0.2 + 0.1*this.glyphs.killing_spree)*ks_uptime);
		dps_oh_crit *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff) * (1 + (0.2 + 0.1*this.glyphs.killing_spree)*ks_uptime); 
		dps.put("oh", dps_oh);
		
		double dps_ss = aps.get("sinister_strike") * get_ss_dmg();
		int t11_2pc = gearbuffs.get("t11_2pc");
		double crit_mult = (((2*(1+0.03*1))-1)*(1+0.1*this.stats.talents.get("lethality"))+1);
		double dps_ss_crit = dps_ss * crit_mult * (this.stats.get_crit_chance()+0.05*t11_2pc);
		dps_ss = dps_ss *(1-this.stats.get_crit_chance()-0.05*t11_2pc) + dps_ss * crit_mult * (this.stats.get_crit_chance()+0.05*t11_2pc);
		dps_ss *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff);
		dps_ss_crit *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff);
		dps.put("sinister_strike", dps_ss);
		
		double dps_rvs = aps.get("revealing_strike") * get_rvs_dmg();
		double dps_rvs_crit = dps_rvs * 2.06 * this.stats.get_crit_chance();
		dps_rvs = dps_rvs *(1-this.stats.get_crit_chance()) + dps_rvs * 2.06 * this.stats.get_crit_chance();
		dps_rvs *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff);
		dps_rvs_crit *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff);
		dps.put("revealing_strike", dps_rvs);
		
		double dps_ks_mh = aps.get("killing_spree") * get_ks_mh_dmg();
		double dps_ks_mh_crit = dps_ks_mh * 2.06 * this.stats.get_crit_chance();
		dps_ks_mh = dps_ks_mh *(1-this.stats.get_crit_chance()) + dps_ks_mh * 2.06 * this.stats.get_crit_chance();
		dps_ks_mh *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff);
		dps_ks_mh_crit *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff);

		double dps_ks_oh = aps.get("killing_spree") * get_ks_oh_dmg();
		double dps_ks_oh_crit = dps_ks_oh * 2.06 * this.stats.get_crit_chance();
		dps_ks_oh = dps_ks_oh *(1-this.stats.get_crit_chance()) + dps_ks_oh * 2.06 * this.stats.get_crit_chance();
		dps_ks_oh *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff);
		dps_ks_oh_crit *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff); 
		
		double dps_ks = dps_ks_mh + dps_ks_oh;
		dps.put("killing_spree", dps_ks);
		
		double evi_crit_chance = t11_4_uptime*1+(1-t11_4_uptime)*(this.stats.get_crit_chance()+0.1*this.glyphs.eviscerate);
		double dps_evi = aps.get("eviscerate") * get_evi_dmg();
		double dps_evi_crit = dps_evi * 2.06 * evi_crit_chance;
		dps_evi = dps_evi *(1-evi_crit_chance) + dps_evi * 2.06 * evi_crit_chance;
		dps_evi *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff);
		dps_evi_crit *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff);
		dps.put("eviscerate", dps_evi);
		
		double dps_dp = aps.get("deadly_poison") * get_dp_dmg(5);
		//System.out.println("dp: " + dps_dp * 1.03 * 1.08 * (1 + avg_bg_buff) / aps.get("deadly_poison"));
		double dps_dp_crit = dps_dp * (1.5*(1+0.03)) * this.stats.get_spell_crit_chance();
		//System.out.println("dp crit: " + dps_dp_crit * 1.03 * 1.08 * (1 + avg_bg_buff) /  this.stats.get_spell_crit_chance() / aps.get("deadly_poison"));
		dps_dp = dps_dp *(1-this.stats.get_spell_crit_chance()) + dps_dp * (1.5*(1+0.03)) * this.stats.get_spell_crit_chance();
		dps_dp *= 1.03 * 1.08 * (1 + avg_bg_buff);
		dps_dp_crit *= 1.03 * 1.08 * (1 + avg_bg_buff);
		dps.put("deadly_poison", dps_dp);
		
		double dps_mg = aps.get("main_gauche") * get_mg_dmg();
		double dps_mg_crit = dps_mg * 2.06 * this.stats.get_crit_chance();
		dps_mg = dps_mg *(1-this.stats.get_crit_chance()) + dps_mg * 2.06 * this.stats.get_crit_chance();
		dps_mg *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff) * (1 + (0.2 + 0.1*this.glyphs.killing_spree)*ks_uptime);
		dps_mg_crit *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + avg_bg_buff) * (1 + (0.2 + 0.1*this.glyphs.killing_spree)*ks_uptime);
		dps.put("main_gauche", dps_mg);
		
		double dps_ip = aps.get("instant_poison") * get_ip_dmg();
		double dps_ip_crit = dps_ip * (1.5*(1+0.03)) * this.stats.get_spell_crit_chance();
		dps_ip = dps_ip *(1-this.stats.get_spell_crit_chance()) + dps_ip * (1.5*(1+0.03)) * this.stats.get_spell_crit_chance();
		dps_ip *= 1.03 * 1.08 * (1 + avg_bg_buff) * (1 + (0.2 + 0.1*this.glyphs.killing_spree)*ks_uptime);
		dps_ip_crit *= 1.03 * 1.08 * (1 + avg_bg_buff) * (1 + (0.2 + 0.1*this.glyphs.killing_spree)*ks_uptime);
		dps.put("instant_poison", dps_ip);
		
		int trinket_hurricane = this.gearbuffs.get("trinket_hurricane");
		double trinket_hurricane_mh_uptime = this.mh.speed*1/60*trinket_hurricane;
		double trinket_hurricane_oh_uptime = this.oh.speed*1/60*trinket_hurricane;
		double mh_hits_per_sec = this.mh_swings_per_sec * this.stats.get_mh_hit_chance(race,mh) + aps.get("main_gauche") + aps.get("sinister_strike") + aps.get("revealing_strike") + aps.get("eviscerate") + aps.get("killing_spree");
		double oh_hits_per_sec = this.oh_swings_per_sec * this.stats.get_oh_hit_chance(race,oh) + aps.get("killing_spree");
		double trinket_hurricane_mh_procs = trinket_hurricane_mh_uptime*mh_hits_per_sec*this.stats.get_spell_hit_chance();
		double trinket_hurricane_oh_procs = trinket_hurricane_oh_uptime*oh_hits_per_sec*this.stats.get_spell_hit_chance();
		double dps_trinket_hurricane = (trinket_hurricane_mh_procs+trinket_hurricane_oh_procs)*(5250+8750)/2;
		dps_trinket_hurricane *= 1.03 * (1 + avg_bg_buff);
		if(trinket_hurricane == 1)
			dps.put("trinket_hurricane", dps_trinket_hurricane);
		
		// vial of shadows
		int trinket_vial_of_shadows_lfr = this.gearbuffs.get("vial_of_shadows_lfr");
		int trinket_vial_of_shadows = this.gearbuffs.get("vial_of_shadows");
		int trinket_vial_of_shadows_heroic = this.gearbuffs.get("vial_of_shadows_heroic");
		double trinket_vos_procs_per_sec = 1 / (9 + 1 / (total_hits * 0.45) );
		double trinket_vos_damage = 5997 + 0.3592 * this.stats.get_ap();
		double trinket_vos_damage_lfr = 5313 + 0.3181 * this.stats.get_ap();
		double trinket_vos_damage_heroic = 6770 + 0.4055 * this.stats.get_ap(); // TODO LFR and implement it in subtlety
		double trinket_vos_crit_damage = trinket_vos_damage * 2.06;
		double trinket_vos_crit_damage_heroic = trinket_vos_damage_heroic * 2.06;
		double trinket_vos_crit_damage_lfr = trinket_vos_damage_lfr * 2.06;
		double trinket_vos_avg_dmg = trinket_vos_damage * (1-this.stats.get_crit_chance()) + trinket_vos_crit_damage * this.stats.get_crit_chance();
		double trinket_vos_avg_dmg_heroic = trinket_vos_damage_heroic * (1-this.stats.get_crit_chance()) + trinket_vos_crit_damage_heroic * this.stats.get_crit_chance();
		double trinket_vos_avg_dmg_lfr = trinket_vos_damage_lfr * (1-this.stats.get_crit_chance()) + trinket_vos_crit_damage_lfr * this.stats.get_crit_chance();
		double trinket_vos_dps = trinket_vial_of_shadows * trinket_vos_procs_per_sec * trinket_vos_avg_dmg;
		trinket_vos_dps		  += trinket_vial_of_shadows_heroic * trinket_vos_procs_per_sec * trinket_vos_avg_dmg_heroic;
		trinket_vos_dps		  += trinket_vial_of_shadows_lfr * trinket_vos_procs_per_sec * trinket_vos_avg_dmg_lfr;
		trinket_vos_dps *= 1.03 * 1.04 * this.stats.get_eff_dmg() * (1 + (0.2 + 0.1*this.glyphs.killing_spree)*ks_uptime);
		if(trinket_vial_of_shadows == 1 || trinket_vial_of_shadows_heroic == 1 || trinket_vial_of_shadows_lfr == 1)
			dps.put("trinket_vial_of_shadows", trinket_vos_dps);
		
		// nokaled 403
		int no_kaled = this.gearbuffs.get("no_kaled");
		double no_kaled_valid_hits = aps.get("mh") + aps.get("sinister_strike") + aps.get("revealing_strike") + aps.get("eviscerate") + aps.get("killing_spree");
		double no_kaled_procs_per_sec = 1/(2 + 1/(0.065 * no_kaled_valid_hits));
		double no_kaled_damage =  (7654+11481)/2;
		double no_kaled_crit_damage = no_kaled_damage * (1.5*(1+0.03));
		double no_kaled_avg_dmg = no_kaled_damage * (1-this.stats.get_spell_crit_chance()) + no_kaled_crit_damage * this.stats.get_spell_crit_chance();
		double no_kaled_dps = no_kaled * no_kaled_procs_per_sec * no_kaled_avg_dmg;
		no_kaled_dps *= 1.03 * 1.08 * (1 + avg_bg_buff) * (1 + (0.2 + 0.1*this.glyphs.killing_spree)*ks_uptime);
		if(no_kaled == 1)
			dps.put("no_kaled", no_kaled_dps);
		
		// t12_2pc
		int t12_2pc = this.gearbuffs.get("t12_2pc");
		double dps_crit = dps_mh_crit + dps_oh_crit + dps_mg_crit + dps_ks_mh_crit + dps_ks_oh_crit + dps_ss_crit + dps_rvs_crit + dps_evi_crit + dps_dp_crit + dps_ip_crit;
		double dps_t12_2pc = t12_2pc*0.06*dps_crit;
		if(t12_2pc == 1){
			dps.put("burning_wounds", dps_t12_2pc);
		}
		double dps = dps_mh + dps_oh + dps_mg + dps_ks + dps_ss + dps_rvs + dps_evi + dps_dp + dps_ip + dps_trinket_hurricane + trinket_vos_dps + no_kaled_dps + dps_t12_2pc;
		super.dps1 = dps;
		return dps;
	}
	
	public void breakdown()
	{
		int max_length = 0;
		for(String name: dps.keySet()) {
			if (name.length() > max_length)
				max_length = name.length();
		}
		for(String name: dps.keySet()) {
			System.out.print(name);
			double ll = max_length - name.length() + 6;
			while(ll > 0) {
				System.out.print(" "); ll--;
			}
			double perc = ((double)Math.round(dps.get(name)/this.dps1*10000))/100;
			System.out.print(dps.get(name) + "   " + perc + "%");
			System.out.println("");
		}
	}
	
	public double get_energy_regen()
	{
		double haste_mod = 0.01*this.stats.get_haste_by_conversion();
		double mg_cp = 0.;
		if(aps.get("main_gauche") != null)
			mg_cp = aps.get("main_gauche")*0.2*5*this.stats.talents.get("combat_potency");
		double combat_potency = get_total_speed()/this.oh.speed * this.stats.get_oh_hit_chance(race,oh) * this.stats.talents.get("combat_potency");
		double regen = 12.5 * (1+haste_mod) + combat_potency + mg_cp;
		return regen;
	}
	
	public double get_total_speed()
	{
		double t13_4pc = this.gearbuffs.get("t13_4pc");
		double speed = 1.4*(1+0.01*this.stats.get_haste_by_conversion())*(1 + 0.02 * this.stats.talents.get("lightning_reflexes"))*1.1*(1+0.2*1*(15+5*this.glyphs.adrenaline_rush+3*t13_4pc)/ar_actual_cd)*(1+0.3*1*40/FIGHT_LENGTH);
		return speed;
	}
	
	public void calc_avg_rvs_per_finisher()
	{
		// probabilities
		double cp0_1_3_5 = 0.8*0.2*0.2;
		double cp0_1_2_3_5 = 0.8*0.8*0.8*0.2;
		double cp0_2_3_5 = 0.2*0.8*0.2;
		double cp0_1_2_4 = 0.8*0.8*0.2;
		double cp0_1_2_3_4 = 0.8*0.8*0.8*0.8;
		double cp0_1_3_4 = 0.8*0.2*0.8;
		double cp0_2_3_4 = 0.2*0.8*0.8;
		double cp0_2_4 = 0.2*0.2;
		double no_ruth_4cp = cp0_1_2_4 + cp0_1_2_3_4 + cp0_1_3_4 + cp0_2_3_4 + cp0_2_4;
		double no_ruth_5cp = cp0_1_3_5 + cp0_1_2_3_5 + cp0_2_3_5;
		
		double cp1_3_5 = 0.2*0.2;
		double cp1_2_3_5 = 0.8*0.8*0.2;
		double cp1_2_3_4 = 0.8*0.8*0.8;
		double cp1_2_4 = 0.8*0.2;
		double cp1_3_4 = 0.2*0.8;
		double with_ruth_4cp = cp1_2_3_4 + cp1_2_4 + cp1_3_4;
		double with_ruth_5cp = cp1_3_5 + cp1_2_3_5;
		
		double ruthlessness = 0.2 * this.stats.talents.get("ruthlessness");
		double use_rs = ruthlessness * with_ruth_4cp + (1-ruthlessness) * no_ruth_4cp;
		double use_no_rs = ruthlessness * with_ruth_5cp + (1-ruthlessness) * no_ruth_5cp;

		// needed ss/rvs
		double no_ruth_needed_ss_4cp = 3 * cp0_1_2_4 + 4 * cp0_1_2_3_4 + 3 * cp0_1_3_4 + 3 * cp0_2_3_4 + 2 * cp0_2_4;
		no_ruth_needed_ss_4cp /= no_ruth_4cp;

		double no_ruth_needed_ss_5cp = 3 * cp0_1_3_5 + 4 * cp0_1_2_3_5 + 3 * cp0_2_3_5;
		no_ruth_needed_ss_5cp /= no_ruth_5cp;

		double with_ruth_needed_ss_4cp = 3 * cp1_2_3_4 + 2 * cp1_2_4 + 2 * cp1_3_4;
		with_ruth_needed_ss_4cp /= with_ruth_4cp;
		double with_ruth_needed_ss_5cp = 2 * cp1_3_5 + 3 * cp1_2_3_5;
		with_ruth_needed_ss_5cp /= with_ruth_5cp;
		
		avg_ss_for_4cp = ruthlessness * with_ruth_needed_ss_4cp + (1-ruthlessness) * no_ruth_needed_ss_4cp;
		avg_rs_for_4cp = 1;
		avg_ss_for_5cp = ruthlessness * with_ruth_needed_ss_5cp + (1-ruthlessness) * no_ruth_needed_ss_5cp;
		
		avg_ss = avg_ss_for_4cp * use_rs + avg_ss_for_5cp * use_no_rs;
		avg_rs = use_rs;
		double t13_2pc = this.gearbuffs.get("t13_2pc");
		ss_energycost = (45-45*0.2*6/31*t13_2pc)*(0.8+0.2/this.stats.get_style_hit_chance(race,mh)) - 2 * this.stats.talents.get("imp_sinister_strike");
		rvs_energycost = (40-40*0.2*6/31*t13_2pc)*(0.8+0.2/this.stats.get_style_hit_chance(race,mh));
		avg_energycost = avg_ss * ss_energycost + avg_rs * rvs_energycost;
	}
	
	public double get_evi_dmg()
	{
		double[] ranks_aggression = new double[]{0.0,0.07,0.14,0.20};
		double aggression = ranks_aggression[this.stats.talents.get("aggression")];
		double[] ranks_coup_de_grace = new double[]{0.0,0.07,0.14,0.20};
		double coup_de_grace = ranks_coup_de_grace[this.stats.talents.get("coup_de_grace")];
		double damage = ((184+550)/2+(370+this.stats.get_ap()*0.091)*5)*(1+aggression+coup_de_grace);
		if(this.stats.talents.isCombat())
			damage *= (1+(0.35+0.1*this.glyphs.revealing_strike)*avg_rs);
		return damage;
	}
}
